<?php

namespace App\Console;

use App\Common\Libs\ArrayHelper;
use App\Common\Libs\IdBuilder;
use App\Common\Libs\Json;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Class BaseCommand
 * @package App\Console
 */
class BaseCommand extends Command
{

    /**
     * @var string $id
     */
    protected static $id;

    /**
     * @var string $mode
     */
    protected static $mode;

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
        if (empty(self::$id)) self::$id = IdBuilder::getUniqueID();
        if (empty(self::$mode)) self::$mode = 'CMD';
    }

    /**
     * Dispatch commands
     *
     * [!] 默认使用队列派发，可选： queue 队列派发 | sync 同步派发
     * @param array $params
     * @param string $driver  queue|sync
     * @return \Illuminate\Foundation\Bus\PendingDispatch|int
     */
    public static function dispatch($params = [], $driver = 'queue')
    {
        $id = self::$id ? : 0;
        $mode = self::$mode = 'WEB';
        $command = self::getCommandName();
        $data = ArrayHelper::merge(['id' => $id, 'command' => $command], ['data' => $params], ['driver' => $driver]);
        Log::channel('command')->info( "[COMMAND-DIP][$mode] " . static::class . " : " . json_encode($data));
        if ('queue' == $driver) {
            return Artisan::queue($command, $params);
        }
        return Artisan::call($command, $params);
    }

    /**
     * Get command name
     *
     * @return array|string
     */
    public static function getCommandName()
    {
        $signature = (new static)->signature;
        if (false !== strpos($signature, ' ')) {
            $signature = explode(' ', $signature);
            return $signature[0];
        }
        return $signature;
    }

    /**
     * Execute the console command.
     *
     * @param  \Symfony\Component\Console\Input\InputInterface  $input
     * @param  \Symfony\Component\Console\Output\OutputInterface  $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $method = method_exists($this, 'handle') ? 'handle' : '__invoke';
        $result = $this->laravel->call([$this, $method]);
        return (int) $result;
    }

    /**
     * Execute console command.
     *
     * @return int
     */
    public function handle()
    {
        list($id, $mode) = [self::$id, self::$mode];
        $data = ArrayHelper::merge(['id' => $id, 'argument' => ArrayHelper::remove($this->arguments(), ['0'])], ['option' => $this->options()]);
        Log::channel('command')->info( "[COMMAND-HDL][$mode] " . static::class . " : " . json_encode($data));
        $result = (int) $this->_handler();
        $data = ['id' => $id, 'result' => $result];
        Log::channel('command')->info( "[COMMAND-RES][$mode] " . static::class . " : " . json_encode($data));
        return $result;
    }

    /**
     * Failed commands
     *
     * @param \Exception $exception
     * @param int $id
     * @return bool
     */
    public function failed(\Exception $exception, $id = 0)
    {
        $id = self::$id ? : $id;
        $message = $exception->getMessage();
        $extra = false === strpos($message, '@file');
        $extra && $message .= ' @file: ' . $exception->getFile().':'.$exception->getLine();
        $this->error($message);
        $data = ArrayHelper::merge(['id' => $id, 'data' => $this->getParams()], ['message' => $message]);
        Log::channel('command')->info( "[COMMAND-FAL] " . static::class . " : " . json_encode($data));
        return true;
    }

    /**
     * Get params
     *
     * [!] 获取参数方法
     * @return array
     */
    public function getParams()
    {
        return ArrayHelper::merge(ArrayHelper::remove($this->arguments(), ['0']), $this->options());
    }

    /**
     * Command _handler
     *
     * @return int
     */
    private function _handler()
    {
        $params = $this->getParams();

        try {
            // 处于 Controller 目录下的命令一般都带有 action 参数，可以直接返回处理
            $method = $params['action'] ?? 'handler';
            $data = Json::decode($params['json'] ?? '', true) ? : [];
            $result = $this->$method($data);
        } catch (\Exception $e) {
            $this->failed($e, self::$id);
            $result = 0;
        }

        return $result;
    }

    /**
     * Command handler
     *
     * [!] 子类继承处理逻辑使用此方法
     * @return int
     */
    public function handler()
    {
        return 1;
    }

    /**
     * Command handler
     *
     * [!]  事物 回滚【一定要在事物里调用】
     * @param string $message 输出错误
     * @return int
     */
    public function throwError($message)
    {
        DB::rollBack();
        return $message;
    }


    /**
     * 执行所有 action
     * @return int
     */
    public function all()
    {
        $methods = $this->getMethods();
        foreach ($methods as $key => $method) {
            $this->info(PHP_EOL . "start $method ..." . PHP_EOL);
            $this->$method();
            $this->info(PHP_EOL . "end $method ." . PHP_EOL);
        }
        return 1;
    }

    /**
     * get class list
     * @param string $dir
     * @param string $namespace
     * @return array
     */
    protected function getClassList($dir = __DIR__, $namespace = __NAMESPACE__)
    {
        $classList = [];
        foreach (scandir($dir) as $file) {
            $file = explode('.', basename($file));
            $file = $file[0];
            if (!in_array($file, ['', basename(static::class)])) {
                $classList[] = $namespace . '\\'. $file;
            }
        }
        return $classList;
    }

    /**
     * get self methods
     * @param string $parent
     * @return array
     */
    protected function getMethods($parent = __CLASS__)
    {
        $self = get_class_methods(static::class);
        $parent = get_class_methods($parent);
        $methods = array_diff($self, $parent);
        foreach ($methods as $key => $method) {
            if ($method === 'all' || false !== strpos($method, '_')) {
                unset($methods[$key]);
            }
        }
        return ArrayHelper::merge($methods, []);
    }

    /**
     * list methods
     * @return int
     */
    protected function list()
    {
        $methods = $this->getMethods();
        $this->info(' action list : ');
        foreach ($methods as $key => $method) {
            $this->info("   ".($key + 1).". $method");
        }
        return 1;
    }

    /**
     * log info
     * @param $msg
     * @param bool $out
     * @param string $channel
     */
    protected function logger($msg, $out = false, $channel = 'command')
    {
        $msg = !is_string($msg) ? Json::encode($msg) : $msg;
        Log::channel($channel)->info( "[COMMAND-INF] " . static::class . " : " . $msg);
        if ($out) $this->info($msg);
    }

}
